跳到主要内容

Redis 客户端 Jedis 学习

配置环境

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>

使用例

@Test
public void test() {
// 1. 获取连接(默认就是填写的这个)
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 2. 操作
jedis.set("username", "alsritter");
System.out.println(jedis.get("username"));
// 3. 关闭连接
jedis.close();
}

自增操作

incr,incrBy,incrByFloat,decr 和 decrBy 方法

首先,前三个方法都是用于递增操作,对于 incr 方法来说,是将 key 中储存的数字加 1,如果 key 不存在,key 的值会先初始化为 0 再加 1,不过需要注意的是:

这是一个针对字符串的操作,因为 Redis 没有专用的整数类型,所以 key 内储存的字符串被解释为十进制 64 位有符号整数来执行 INCR 操作,所以该操作的值限制在 64 位(bit)有符号数字表示之内。

public Long incr(final String key)
public Long incrBy(final String key, final long integer)
public Double incrByFloat(final String key, final double value)
public Long decr(final String key)
public Long decrBy(final String key, final long integer)

而 incrBy 方法,是为 key 中储存的数字加上增量 integer,另一个方法 incrByFloat 是为 key 中储存的数字加上浮点增量 value,其他情况都是和 incr 方法相同的。

而针对 incrByFloat,无论是 key 的值,还是增量 increment ,都可以使用像 2.0e7 、3e5 、90e-2 那样的指数符号(exponential notation)来表示。

另外的两个方法 decr 和 decrBy 方法则是用于递减操作,一个是每次减1,另一个是每次减固定的数字。

设置一个可以过期的 key

expire,pexpire,expireAt,和 pexpireAt 方法

// 该方法用于设置 key 的过期时间,在 Redis 中,带有生存时间的 key 被称为 volatile。
public Long expire(final String key, final int seconds)
// 和expire方法类似,但该方法设置过期时间的单位是毫秒,而不是秒。
public Long pexpire(final String key, final long milliseconds)

而 expireAt 方法,该方法和 expire 方法类似,都是用于为 key 设置生存时间,不同在于该方法接受的时间参数是 UNIX 时间戳 unix timestamp,单位是秒;

而 pexpireAt 方法同样和 expireAt 方法类似,不同的是该方法以毫秒为单位设置过期时间的UNIX时间戳。

设置 Key 并不覆盖(Setnx)

Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值,但是这个值已经存在时,会覆盖失败

redis Setnx 命令基本语法如下:

redis 127.0.0.1:6379> SETNX KEY_NAME VALUE

返回值

  • 设置成功,返回 1
  • 设置失败,返回 0

实例

redis> EXISTS job                # job 不存在
(integer) 0

redis> SETNX job "programmer" # job 设置成功
(integer) 1

redis> SETNX job "code-farmer" # 尝试覆盖 job ,失败
(integer) 0

redis> GET job # 没有被覆盖
"programmer"

这个命令比较常用来构建分布式锁

操作 Map 类型的数据

常用方法:

// 方法表示为哈希表中域 field 的值递增常量 value;增量也可以为负数,相当于对给定域进行减法操作;
public Long hincrBy(final String key, final String field, final long value)

// 为哈希表 key 中的域 field 递增浮点数增量 value,如果 hash 表中没有域,那么会先将该域的值设为0,
// 然后再递增;如果没有key,则会先创建一个hash表;同样,value也可以是负数,相当于进行递减操作;
public Double hincrByFloat(final String key, final String field, final double value)

使用例子:

@Test
public void test() {
// 1. 获取连接(默认就是填写的这个)
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 2. 操作
jedis.hset("user", "name", "Lily");
jedis.hset("user", "age", "123");
jedis.hset("user", "gender", "female");

// 获取数据
String name = jedis.hget("user", "name");
System.out.println(name);

// 或直接获取 Map 集合
Map<String, String> user = jedis.hgetAll("user");
user.forEach((k, v) -> {
System.out.println(k + "------" + v);
});

// 3. 关闭连接
jedis.close();
}

操作 List 类型的数据

@Test
public void test() {
// 1. 获取连接(默认就是填写的这个)
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 2. 操作
jedis.lpush("tempList", "1", "2", "3");
jedis.rpush("tempList", "7", "8", "9");

System.out.println(jedis.lpop("tempList") + " 这是弹出的标识");

// 返回的是一个 List<String> 类型的 List
jedis.lrange("tempList", 0, -1).forEach(System.out::println);

// 3. 关闭连接
jedis.close();
}

操作 Set 类型数据

@Test
public void test() {
// 1. 获取连接(默认就是填写的这个)
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 2. 操作
// 这样插入只插入了 1、2、3、4 并不会出现重叠(也不会报错)
jedis.sadd("testSet","1","2","2","3","4");

Set<String> testSet = jedis.smembers("testSet");

testSet.forEach(v->{
System.out.println("这是" + v);
});
// 3. 关闭连接
jedis.close();
}

设置一个可以过期的 key(单位是秒)

activecode:123456 键值对存入 redis,并且 5 秒后自动删除该键值对

jedis.setex("activecode",5,"123456");

这个方法可以存储一些有时效的数据,例如激活码之类的

操作 SortSet 类型数据

常用方法:

// 为集合 key 中成员 member 的 score 值加上增量,并调整位置,参数 score 可以是负数,表示递减
public Double zincrby(final String key, final double score, final String member)
public Double zincrby(String key, double score, String member, ZIncrByParams params)

// 移除集合 key 中指定区间内的成员,包含start,end,相当于下标
// 删除排序集中的所有元素,其排名在开始和结束之间。
// 开始和结束从 0 开始,排名 0 是得分最低的元素。
// start 和 end 都可以是负数,它们表示从具有最高等级的元素开始的偏移量。
// 例如:-1表示最后一个成员,-2表示倒数第二个成员,以此类推(注意:排序是从小到大的)
// 例如:conn.zremrangeByRank("viewed:" + token, 0, -26); 就是从大到小取 0 ~ 26 之间的元素
public Long zremrangeByRank(final String key, final long start, final long end)
// 移除集合key中一个或多个元素
public Long zrem(final String key, final String... members)


// 返回集合key中某一个元素在该集合内的排名,按照 score 值从小到大排列
public Long zrank(final String key, final String member)

使用例子:

@Test
public void test() {
// 1. 获取连接(默认就是填写的这个)
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 2. 操作
// 排序的数据类型只能一次插入一个数据
jedis.zadd("testSortSet", 10, "1");
jedis.zadd("testSortSet", 18, "5");
jedis.zadd("testSortSet", 14, "4");
jedis.zadd("testSortSet", 13, "3");
jedis.zadd("testSortSet", 12, "2");

Set<String> testSortSet = jedis.zrange("testSortSet", 0, -1);
testSortSet.forEach(v->{
System.out.println("这是" + v);
});
// 3. 关闭连接
jedis.close();
}

求并集

zunionstore 方法用于计算一个或多个集合的并集,并将并集结果储存到 dstkey 集合中,默认情况下,结果集中某个成员的 score 值是所有给定集下该成员 score 的值之和(如果并集的是一个排序数组和一个非排序数组,那非排序数组的 score 值默认为 1)。

当然有一个选项 AGGREGATE 可以设置指定 score 的模式,也就是通过参数 ZParams 的枚举对象 Aggregate 来指定具体的模式:

SUM,就是计算所有的和; MIN,取最小值,MAX,取最大值;

示例:

ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX);
// zinterstore 用来求并集
conn.zinterstore(key, params, "group:" + group, order);

另外,还有一个选项 WEIGHTS 可以为有序集合指定一个乘法因子(multiplication factor),每个给定有序集的所有成员的 score 值都会乘以该有序集的因子。

// 所以遇到需要操作成员的 score 时也可以使用这个参数
ZParams params = new ZParams().weights(0.5);
// 将 score 降低为原来的一半
conn.zinterstore("viewed:", params);

如果没有指定 WEIGHTS 选项,乘法因子默认设置为 1 ,其中该选项通过 ZParams 的 weightsByDouble 方法来指定。

public Long zunionstore(final String dstkey, final String... sets)
public Long zunionstore(final String dstkey, final ZParams params, final String... sets)

而另一个方法 zinterstore 则是用于计算一个和多个集合的交集,并将结果保存到 dstkey 集合中,其他参数则和 zunionstore 方法是一致的:

public Long zinterstore(final String dstkey, final String... sets)
public Long zinterstore(final String dstkey, final ZParams params, final String... sets)

连接池

Jedis 不是线程安全的,故不应该在多线程环境中共用一个 Jedis 实例。

不像 JDBC 或 MyBatis 那样需要使用第三方连接池,jedis 自带了连接池 JedisPool

public class TestJedis {

// 配置连接池(使用静态方法)
public static JedisPool getPool(){
// 0. 还可以给连接池创建一个配置类
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 设置最大连接数
jedisPoolConfig.setMaxTotal(50);
// 设置最大的空闲连接
jedisPoolConfig.setMaxIdle(10);
// 1. 获取连接池对象(可以传入一个 Config 对象)
return new JedisPool(jedisPoolConfig,"127.0.0.1", 6379);
}

@Test
public void test() {

// 2. 获取连接
Jedis jedis = getPool().getResource();
// 使用数据
jedis.lpush("tempList", "1", "2", "3");
jedis.lrange("tempList",0,-1).forEach(System.out::println);

// 3. 释放连接(这个不像之间那种直接创建 jedis 那样释放连接,而是归还连接到连接池)
jedis.close();
}
}

连接池的简单使用

GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
Jedis jedis = null;
try {
//从连接池获取jedis对象
jedis = jedisPool.getResource();
//执行操作
jedis.set("java", "good");
System.out.println(jedis.get("java"));
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
//这里使用的close不代表关闭连接,指的是归还资源
jedisPool.close();
}

}

针对 Key 的操作

这些操作 Key 的方法一般都是通用的,针对所有类型的 key 进行操作的。

// 删除给定的一个或多个 key,如果key不存在,会被忽略
public Long del(final String... keys)

// 返回key存在的数量
public Long exists(final String... keys)
// 查询key是否存在
public Boolean exists(final String key)

// 查找所有符合给定正则表达式的key
public Set<String> keys(final String pattern)

// 移除给定 key 的生存时间,将这个 key 从带生存时间的key 转换成永久的永不过期的 key;
public Long persist(final String key)

// 以秒为单位,返回key的剩余生存时间
public Long pttl(final String key)

// 以毫秒为单位,返回key的剩余生存时间
public Long ttl(final String key)

// 从当前库中随机返回一个key
public String randomKey()

// 返回key所储存的值的类型,返回的类型如"string", "list", "set"
public String type(final String key)

// 修改key的名称,如果newKey已经存在,将会覆盖掉;如果oldkey与newkey相同或者key不存在时,异常;
public String rename(final String oldkey, final String newkey)

// 当且仅当 newkey 不存在时,将 oldkey 改名为 newkey;而如果oldkey不存在,异常;
public Long renamenx(final String oldkey, final String newkey)

// 将当前库中的key移动到另一个库中,如果当前库和给定库有相同名字的给定key,或者key不存在于当前库,
// 那么 MOVE 操作没有任何效果。因此,也可以利用这一特性,将 MOVE 当作锁(locking)原语(primitive);
public Long move(final String key, final int dbIndex)

创建连接池工具类

同 JDBC 连接池一样,一般把配置信息丢到 Properties 文件上

编写配置文件

host=127.0.0.1
port=6379
maxTotal=50
maxIdle=10

编写连接池工具类

public class JedisPoolUtils {

// 禁止实例化
private JedisPoolUtils(){}

static {
// 读取 Properties 配置文件
InputStream is = JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties");
// 创建 Properties 对象
Properties properties = new Properties();
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
// 获取数据到 JedisPoolConfig 里面去
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(Integer.parseInt(properties.getProperty("maxIdle")));
jedisPoolConfig.setMaxTotal(Integer.parseInt(properties.getProperty("maxTotal")));

// 初始化 JedisPool
jedisPool = new JedisPool(jedisPoolConfig, properties.getProperty("host"),Integer.parseInt(properties.getProperty("port")));
}

private static final JedisPool jedisPool;

public static Jedis getJedis(){
return jedisPool.getResource();
}
}

Lettuce 的使用

TODO: 学习 Lettuce 的使用

学习 Spring 集成 Redis 的时候发现不只有 jedis 这个客户端,还有这个 Lettuce

Lettuce 和 Jedis 一样,都是连接 Redis Server 的客户端程序。

Jedis 在实现上是直连 Redis Server,多线程环境下非线程安全,除非使用连接池,为每个 Jedis 实例增加物理连接。

Lettuce 基于 Netty 的连接实例(StatefulRedisConnection),可以在多个线程间并发访问,且线程安全,满足多线程环境下的并发访问,同时它是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

Reference

Redis客户端Jedis方法介绍(一)